package com.p7.framework.permission.shiro.session;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import javax.annotation.Resource;

import com.p7.framework.permission.shiro.utils.SerializeUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.ValidatingSession;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;


/**
 * session to redis
 * @ClassName: ClientRedisSessionDAO 
 * @author yz
 * @date 2019年2月13日 下午5:04:03
 */
public class ClientRedisSessionDAO extends AbstractSessionDAO {

	private static Logger logger = LoggerFactory.getLogger(ClientRedisSessionDAO.class);

	@Resource
	private RedisTemplate<String, Object> redisTemplate;

	private long expire;

	private String keyPrefix;

	/**
	 * 更新 session 到 redis
	 * @Title: update 
	 * @see org.apache.shiro.session.mgt.eis.SessionDAO#update(org.apache.shiro.session.Session) 
	 * @param session
	 * @throws UnknownSessionException  
	 * @date 2019年2月14日 下午3:51:15
	 * @author yz
	 */
	@Override
	public void update(Session session) throws UnknownSessionException {
		if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {
			logger.error("session isValid and don't update......");
			return;
		}
		this.saveSession(session);
	}

	/**
	 * 保存 session 到 redis
	 * @Title: saveSession 
	 * @param session
	 * @throws UnknownSessionException  
	 * @date 2019年2月14日 下午3:53:05
	 * @author yz
	 */
	private void saveSession(Session session) throws UnknownSessionException {
		if (session == null || session.getId() == null) {
			logger.error("session or session id is null");
			return;
		}
		// 负数为不超时 毫秒 shiro会转换为s,且舍弃小数部分
		session.setTimeout(getExpire() * 1000);
		final byte[] key = getByteKey(session.getId());
		final byte[] value = SerializeUtils.serialize(session);
		redisTemplate.execute(new RedisCallback<Object>() {
			@Override
			public Object doInRedis(RedisConnection connection) throws DataAccessException {
				connection.set(key, value);
				connection.expire(key, getExpire());
				return null;
			}
		});

		logger.info("ClientRedisSessionDAO saveSession session success,key:{}", key);
	}

	/**
	 * 删除 redis 中的 session
	 * @Title: delete 
	 * @see org.apache.shiro.session.mgt.eis.SessionDAO#delete(org.apache.shiro.session.Session) 
	 * @param session  
	 * @date 2019年2月14日 下午3:57:01
	 * @author yz
	 */
	@Override
	public void delete(Session session) {
		if (session == null || session.getId() == null) {
			logger.error("session or session id is null");
			return;
		}
		final byte[] byteKey = getByteKey(session.getId());
		String key = new String(byteKey);
		redisTemplate.execute(new RedisCallback<Object>() {
			@Override
			public Object doInRedis(RedisConnection connection) throws DataAccessException {
				connection.del(byteKey);
				return null;
			}
		});
		logger.info("ClientRedisSessionDAO delete session success,key:{}", key);
	}

	@Override
	public Collection<Session> getActiveSessions() {
		Set<Session> sessions = new HashSet<Session>();
		final Set<byte[]> keys = new HashSet<>();
		Set<String> keystring = redisTemplate.keys(this.keyPrefix + "*");
		for (String key : keystring) {
			keys.add(key.getBytes());
		}

		if (keys.size() > 0) {
			Set<byte[]> set = redisTemplate.execute(new RedisCallback<Set<byte[]>>() {
				@Override
				public Set<byte[]> doInRedis(RedisConnection connection) throws DataAccessException {
					Set<byte[]> result = new HashSet<>();
					for (byte[] key : keys) {
						byte[] bytes = connection.get(key);
						if (bytes != null) {
							result.add(bytes);
						}
					}
					return result;
				}
			});
			for (byte[] value : set) {
				if (value != null && value.length > 0) {
					Session s = (Session) SerializeUtils.deserialize(value);
					sessions.add(s);
				}
			}
		}
		return sessions;
	}

	@Override
	protected Serializable doCreate(Session session) {
		Serializable sessionId = this.generateSessionId(session);
		this.assignSessionId(session, sessionId);
		this.saveSession(session);
		logger.info("ClientRedisSessionDAO doCreate session success...");
		return sessionId;
	}

	@Override
	protected Session doReadSession(Serializable sessionId) {
		if (sessionId == null) {
			logger.error("session id is null");
			return null;
		}
		final byte[] byteKey = getByteKey(sessionId);
		byte[] bytes = redisTemplate.execute(new RedisCallback<byte[]>() {
			@Override
			public byte[] doInRedis(RedisConnection connection) throws DataAccessException {
				return connection.get(byteKey);
			}
		});
		Session session = null;
		session = (Session) SerializeUtils.deserialize(bytes);
		logger.info("ClientRedisSessionDAO doReadSession session success...");
		return session;
	}

	/**
	 * 获得byte[]型的key
	 * @param sessionId
	 * @return
	 */
	private byte[] getByteKey(Serializable sessionId) {
		String preKey = this.keyPrefix + sessionId;
		return preKey.getBytes();
	}

	@Override
	public Session readSession(Serializable sessionId) throws UnknownSessionException {
		if (sessionId == null) {
			logger.error("session id is null");
			return null;
		}
		logger.debug("doReadSession sessionId:" + sessionId);
		final byte[] byteKey = this.getByteKey(sessionId);
		byte[] bytes = redisTemplate.execute(new RedisCallback<byte[]>() {
			@Override
			public byte[] doInRedis(RedisConnection connection) throws DataAccessException {
				return connection.get(byteKey);
			}
		});
		Session s = (Session) SerializeUtils.deserialize(bytes);
		logger.info("ClientRedisSessionDAO readSession session success...");
		return s;
	}

	public String getKeyPrefix() {
		return keyPrefix;
	}

	public void setKeyPrefix(String keyPrefix) {
		this.keyPrefix = keyPrefix;
	}

	public long getExpire() {
		return expire;
	}

	public void setExpire(long expire) {
		this.expire = expire;
	}
}
